package com.yao2san.sim.storage.client.core.manager;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.yao2san.sim.storage.client.util.StorageUtil;
import com.yao2san.sim.storage.client.config.Constants;
import com.yao2san.sim.storage.client.config.LocalProperties;
import com.yao2san.sim.storage.client.core.auth.Credentials;
import com.yao2san.sim.storage.client.core.enums.StorageType;
import com.yao2san.sim.storage.client.util.StorageUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;


public class LocalStorageManager extends AbstractStorageManager {
    private final LocalProperties localProperties;
    private final static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");


    public LocalStorageManager(LocalProperties localProperties) {
        this.localProperties = localProperties;
    }

    @Override
    public boolean exist(String object) {
        StorageUtil.checkObjectName(object);
        return Files.exists(Paths.get(getObject(object)));
    }

    @Override
    public void delete(String object) throws IOException {
        StorageUtil.checkObjectName(object);
        if (object == null || object.isEmpty()) {
            throw new IllegalArgumentException("object can not be null");
        }
        Path path = Paths.get(getPathName(object));
        if (!Files.exists(path) || Files.isDirectory(path)) {
            throw new FileNotFoundException(path + " not found");
        }
        Files.delete(path);
    }

    private String getPathName(String object) {
        return StorageUtil.formatPath(this.localProperties.getBucket() + "/" + object);
    }

    @Override
    public void rename(String source, String target) throws IOException {
        copy(source, target, false);
    }

    @Override
    public void copy(String source, String target, boolean keepSource) throws IOException {
        StorageUtil.checkObjectName(source, target);
        if (source == null || source.isEmpty()) {
            throw new IllegalArgumentException("source object can not be null");
        }
        if (target == null || target.isEmpty()) {
            throw new IllegalArgumentException("target can not be null");
        }
        if (target.equals(source)) {
            throw new IllegalArgumentException("target can not be equals source");
        }
        Path path = Paths.get(getObject(source));
        if (!Files.exists(path) || Files.isDirectory(path)) {
            throw new FileNotFoundException(source + " not found");
        }
        Path newPath = Paths.get(getObject(target));
        if (!keepSource) {
            Files.delete(path);
        }
        Files.copy(path, newPath);
    }

    @Override
    public List<StorageObject> list(String prefix) throws IOException {
        return list(prefix, null);
    }

    @Override
    public List<StorageObject> list(String prefix, String startAfter) throws IOException {
        StorageUtil.checkObjectName(prefix, startAfter);
        if (prefix == null) {
            prefix = "";
        }
        String pathName = StorageUtil.formatPath(this.localProperties.getPath() + "/" + prefix);
        int index = pathName.lastIndexOf("/");
        String dir = pathName.substring(0, index);
        String name = pathName.substring(index + 1);

        Path path = Paths.get(dir);
        if (!Files.exists(path)) {
            return Collections.emptyList();
        }

        List<StorageObject> storageObjects = new ArrayList<>();
        try (Stream<Path> list = Files.list(path)) {
            list.forEach(v -> {
                File file = new File(v.toString());
                String object = StorageUtil.formatPath(v.toString().replace(localProperties.getPath(), ""));
                boolean directory = file.isDirectory();
                boolean start = startAfter == null || object.contains(startAfter);
                if (v.getFileName().toString().startsWith(name) && start) {
                    StorageObject storageObject = StorageObject.builder()
                            .object(object)
                            .size(file.length())
                            .lastModified(file.lastModified())
                            .storageType(StorageType.LOCAL.name())
                            .url(directory ? null : getUrl(v.toString()))
                            .isDir(directory)
                            .build();
                    storageObjects.add(storageObject);
                }
            });
        }
        return storageObjects;
    }

    private String getObject(String object) {
        return StorageUtil.formatPath(object);
    }

    private String getUrl(String fullPath) {
        String sb = "/" +
                localProperties.getPrefix() +
                "/" +
                StorageUtil.formatPath(fullPath).replace(StorageUtil.formatPath(localProperties.getPath()), "");
        return localProperties.getEndpoint() + StorageUtil.formatPath(sb);
    }

    @Override
    public String getName() {
        return StorageType.LOCAL.name();
    }

    @Override
    public Credentials credentials(long durationSeconds) {
        Credentials cachedCredentials = getCachedCredentials();
        if (cachedCredentials != null) {
            return cachedCredentials;
        }
        final String tempAk = UUID.randomUUID().toString();
        final String tempSK = UUID.randomUUID().toString();
        Calendar instance = Calendar.getInstance(Constants.DEFAULT_TIME_ZONE);
        instance.add(Calendar.SECOND, (int) durationSeconds);
        String token = JWT.create().withClaim("endpoint", localProperties.getEndpoint())
                .withClaim("path", localProperties.getPath())
                .withClaim("prefix", localProperties.getPrefix())
                .withClaim("accessKey", tempAk)
                .withClaim("secretKey", tempSK)
                .withExpiresAt(instance.toInstant())
                .sign(Algorithm.HMAC256(localProperties.getSecretKey()));
        Credentials credentials = new Credentials(token, UUID.randomUUID().toString(), UUID.randomUUID().toString(), DATE_FORMAT.format(instance.getTimeInMillis()));
        cachedCredentials(credentials, durationSeconds);
        return credentials;

    }

    public Credentials getCachedCredentials() {
        return CREDENTIALS_CACHE.get(getKey());
    }

    public void cachedCredentials(Credentials credentials, long seconds) {
        CREDENTIALS_CACHE.put(getKey(), credentials, seconds);
    }

    public String getKey() {
        return StorageUtil.md5(this.localProperties.toString());
    }
}
